#include <glib/gprintf.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
+#include <ext2fs/ext2_fs.h>
+#ifdef HAVE_LINUX_FSVERITY_H
+#include <linux/fsverity.h>
+#endif
#include "otutil.h"
#include "ostree.h"
#endif
}
+/* Wrapper around the fsverity ioctl, compressing the result to
+ * "success, unsupported or error". This is used for /boot where
+ * we enable verity if supported.
+ * */
+gboolean
+_ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf,
+ _OstreeFeatureSupport fsverity_requested,
+ gboolean *supported,
+ GError **error)
+{
+ /* Set this by default to simplify the code below */
+ if (supported)
+ *supported = FALSE;
+
+ if (fsverity_requested == _OSTREE_FEATURE_NO)
+ return TRUE;
+
+#ifdef HAVE_LINUX_FSVERITY_H
+ GLNX_AUTO_PREFIX_ERROR ("fsverity", error);
+
+ /* fs-verity requires a read-only file descriptor */
+ if (!glnx_tmpfile_reopen_rdonly (tmpf, error))
+ return FALSE;
+
+ struct fsverity_enable_arg arg = { 0, };
+ arg.version = 1;
+ arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; /* TODO configurable? */
+ arg.block_size = 4096; /* FIXME query */
+ arg.salt_size = 0; /* TODO store salt in ostree repo config */
+ arg.salt_ptr = 0;
+ arg.sig_size = 0; /* We don't currently expect use of in-kernel signature verification */
+ arg.sig_ptr = 0;
+
+ if (ioctl (tmpf->fd, FS_IOC_ENABLE_VERITY, &arg) < 0)
+ {
+ switch (errno)
+ {
+ case ENOTTY:
+ case EOPNOTSUPP:
+ return TRUE;
+ default:
+ return glnx_throw_errno_prefix (error, "ioctl(FS_IOC_ENABLE_VERITY)");
+ }
+ }
+
+ if (supported)
+ *supported = TRUE;
+#endif
+ return TRUE;
+}
+
+/* Enable verity on a file, respecting the "wanted" and "supported" states.
+ * The main idea here is to optimize out pointlessly calling the ioctl()
+ * over and over in cases where it's not supported for the repo's filesystem,
+ * as well as to support "opportunistic" use (requested and if filesystem supports).
+ * */
+gboolean
+_ostree_tmpf_fsverity (OstreeRepo *self,
+ GLnxTmpfile *tmpf,
+ GError **error)
+{
+#ifdef HAVE_LINUX_FSVERITY_H
+ g_mutex_lock (&self->txn_lock);
+ _OstreeFeatureSupport fsverity_wanted = self->fs_verity_wanted;
+ _OstreeFeatureSupport fsverity_supported = self->fs_verity_supported;
+ g_mutex_unlock (&self->txn_lock);
+
+ switch (fsverity_wanted)
+ {
+ case _OSTREE_FEATURE_YES:
+ {
+ if (fsverity_supported == _OSTREE_FEATURE_NO)
+ return glnx_throw (error, "fsverity required but filesystem does not support it");
+ }
+ break;
+ case _OSTREE_FEATURE_MAYBE:
+ break;
+ case _OSTREE_FEATURE_NO:
+ return TRUE;
+ }
+
+ gboolean supported = FALSE;
+ if (!_ostree_tmpf_fsverity_core (tmpf, fsverity_wanted, &supported, error))
+ return FALSE;
+
+ if (!supported)
+ {
+ if (G_UNLIKELY (fsverity_wanted == _OSTREE_FEATURE_YES))
+ return glnx_throw (error, "fsverity required but filesystem does not support it");
+
+ /* If we got here, we must be trying "opportunistic" use of fs-verity */
+ g_assert_cmpint (fsverity_wanted, ==, _OSTREE_FEATURE_MAYBE);
+ g_mutex_lock (&self->txn_lock);
+ self->fs_verity_supported = _OSTREE_FEATURE_NO;
+ g_mutex_unlock (&self->txn_lock);
+ return TRUE;
+ }
+
+ g_mutex_lock (&self->txn_lock);
+ self->fs_verity_supported = _OSTREE_FEATURE_YES;
+ g_mutex_unlock (&self->txn_lock);
+#else
+ g_assert_cmpint (self->fs_verity_wanted, !=, _OSTREE_FEATURE_YES);
+#endif
+ return TRUE;
+}
+
/* Given an O_TMPFILE regular file, link it into place. */
gboolean
_ostree_repo_commit_tmpf_final (OstreeRepo *self,
cancellable, error))
return FALSE;
+ if (!_ostree_tmpf_fsverity (self, tmpf, error))
+ return FALSE;
+
if (!glnx_link_tmpfile_at (tmpf, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST,
dest_dfd, tmpbuf, error))
return FALSE;
#include "libglnx.h"
#include "otutil.h"
#include <glnx-console.h>
+#include <linux/magic.h>
#include "ostree-core-private.h"
#include "ostree-sysroot-private.h"
#include <glib/gstdio.h>
#include <sys/file.h>
#include <sys/statvfs.h>
+#include <sys/statfs.h>
#define REPO_LOCK_DISABLED (-2)
#define REPO_LOCK_BLOCKING (-1)
}
}
+ /* Currently experimental */
+ static const char fsverity_key[] = "ex-fsverity";
+ self->fs_verity_wanted = _OSTREE_FEATURE_NO;
+#ifdef HAVE_LINUX_FSVERITY_H
+ self->fs_verity_supported = _OSTREE_FEATURE_MAYBE;
+#else
+ self->fs_verity_supported = _OSTREE_FEATURE_NO;
+#endif
+ gboolean fsverity_required = FALSE;
+ if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_key, "required",
+ FALSE, &fsverity_required, error))
+ return FALSE;
+ if (fsverity_required)
+ {
+ self->fs_verity_wanted = _OSTREE_FEATURE_YES;
+ if (self->fs_verity_supported == _OSTREE_FEATURE_NO)
+ return glnx_throw (error, "fsverity required, but libostree compiled without support");
+ }
+ else
+ {
+ gboolean fsverity_opportunistic = FALSE;
+ if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_key, "opportunistic",
+ FALSE, &fsverity_opportunistic, error))
+ return FALSE;
+ if (fsverity_opportunistic)
+ self->fs_verity_wanted = _OSTREE_FEATURE_MAYBE;
+ }
+
{
g_clear_pointer (&self->collection_id, g_free);
if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id",
#include "otutil.h"
#include "ostree.h"
+#include "ostree-repo-private.h"
#include "ostree-sysroot-private.h"
#include "ostree-sepolicy-private.h"
#include "ostree-bootloader-zipl.h"
* hardlink if we're on the same partition.
*/
static gboolean
-install_into_boot (OstreeSePolicy *sepolicy,
+install_into_boot (OstreeRepo *repo,
+ OstreeSePolicy *sepolicy,
int src_dfd,
const char *src_subpath,
int dest_dfd,
GCancellable *cancellable,
GError **error)
{
- if (linkat (src_dfd, src_subpath, dest_dfd, dest_subpath, 0) != 0)
- {
- if (G_IN_SET (errno, EMLINK, EXDEV))
- {
- /* Be sure we relabel when copying the kernel, as in current
- * e.g. Fedora it might be labeled module_object_t or usr_t,
- * but policy may not allow other processes to read from that
- * like kdump.
- * See also https://github.com/fedora-selinux/selinux-policy/commit/747f4e6775d773ab74efae5aa37f3e5e7f0d4aca
- * This means we also drop xattrs but...I doubt anyone uses
- * non-SELinux xattrs for the kernel anyways aside from perhaps
- * IMA but that's its own story.
- */
- g_auto(OstreeSepolicyFsCreatecon) fscreatecon = { 0, };
- const char *boot_path = glnx_strjoina ("/boot/", glnx_basename (dest_subpath));
- if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, sepolicy,
- boot_path, S_IFREG | 0644,
- error))
- return FALSE;
- return glnx_file_copy_at (src_dfd, src_subpath, NULL, dest_dfd, dest_subpath,
- GLNX_FILE_COPY_NOXATTRS | GLNX_FILE_COPY_DATASYNC,
- cancellable, error);
- }
- else
- return glnx_throw_errno_prefix (error, "linkat(%s)", dest_subpath);
- }
+ if (linkat (src_dfd, src_subpath, dest_dfd, dest_subpath, 0) == 0)
+ return TRUE; /* Note early return */
+ if (!G_IN_SET (errno, EMLINK, EXDEV))
+ return glnx_throw_errno_prefix (error, "linkat(%s)", dest_subpath);
+
+ /* Otherwise, copy */
+ struct stat src_stbuf;
+ if (!glnx_fstatat (src_dfd, src_subpath, &src_stbuf, AT_SYMLINK_NOFOLLOW, error))
+ return FALSE;
+
+ glnx_autofd int src_fd = -1;
+ if (!glnx_openat_rdonly (src_dfd, src_subpath, FALSE, &src_fd, error))
+ return FALSE;
+
+ /* Be sure we relabel when copying the kernel, as in current
+ * e.g. Fedora it might be labeled module_object_t or usr_t,
+ * but policy may not allow other processes to read from that
+ * like kdump.
+ * See also https://github.com/fedora-selinux/selinux-policy/commit/747f4e6775d773ab74efae5aa37f3e5e7f0d4aca
+ * This means we also drop xattrs but...I doubt anyone uses
+ * non-SELinux xattrs for the kernel anyways aside from perhaps
+ * IMA but that's its own story.
+ */
+ g_auto(OstreeSepolicyFsCreatecon) fscreatecon = { 0, };
+ const char *boot_path = glnx_strjoina ("/boot/", glnx_basename (dest_subpath));
+ if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, sepolicy,
+ boot_path, S_IFREG | 0644,
+ error))
+ return FALSE;
+
+ g_auto(GLnxTmpfile) tmp_dest = { 0, };
+ if (!glnx_open_tmpfile_linkable_at (dest_dfd, ".", O_WRONLY | O_CLOEXEC,
+ &tmp_dest, error))
+ return FALSE;
+
+ if (glnx_regfile_copy_bytes (src_fd, tmp_dest.fd, (off_t) -1) < 0)
+ return glnx_throw_errno_prefix (error, "regfile copy");
+
+ /* Kernel data should always be root-owned */
+ if (fchown (tmp_dest.fd, src_stbuf.st_uid, src_stbuf.st_gid) != 0)
+ return glnx_throw_errno_prefix (error, "fchown");
+
+ if (fchmod (tmp_dest.fd, src_stbuf.st_mode & 07777) != 0)
+ return glnx_throw_errno_prefix (error, "fchmod");
+
+ if (fdatasync (tmp_dest.fd) < 0)
+ return glnx_throw_errno_prefix (error, "fdatasync");
+
+ /* Today we don't have a config flag to *require* verity on /boot,
+ * and at least for Fedora CoreOS we're not likely to do fsverity on
+ * /boot soon due to wanting to support mounting it from old Linux
+ * kernels. So change "required" to "maybe".
+ */
+ _OstreeFeatureSupport boot_verity = _OSTREE_FEATURE_NO;
+ if (repo->fs_verity_wanted != _OSTREE_FEATURE_NO)
+ boot_verity = _OSTREE_FEATURE_MAYBE;
+ if (!_ostree_tmpf_fsverity_core (&tmp_dest, boot_verity, NULL, error))
+ return FALSE;
+
+ if (!glnx_link_tmpfile_at (&tmp_dest, GLNX_LINK_TMPFILE_NOREPLACE, dest_dfd, dest_subpath, error))
+ return FALSE;
return TRUE;
}
return FALSE;
if (errno == ENOENT)
{
- if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath,
+ if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath,
bootcsum_dfd, kernel_layout->kernel_namever,
sysroot->debug_flags,
cancellable, error))
return FALSE;
if (errno == ENOENT)
{
- if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->initramfs_srcpath,
+ if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->initramfs_srcpath,
bootcsum_dfd, kernel_layout->initramfs_namever,
sysroot->debug_flags,
cancellable, error))
return FALSE;
if (errno == ENOENT)
{
- if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
+ if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
bootcsum_dfd, kernel_layout->devicetree_namever,
sysroot->debug_flags,
cancellable, error))
return FALSE;
if (errno == ENOENT)
{
- if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_hmac_srcpath,
+ if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_hmac_srcpath,
bootcsum_dfd, kernel_layout->kernel_hmac_namever,
sysroot->debug_flags,
cancellable, error))